from pytest import fixture
from pytest_atf import *
from pytest_atf.atf_globalvar import globalVar as gl
from atf_log.logger import decorator_for_user_log

import re
import os
import ipaddress
import yaml
import string
from datetime import datetime
from datetime import timezone
import random
from typing import Final,Tuple
# --------用户修改区 -------------------

# 执行层及使用拓扑文件名称
level = 3
topo = r'T_2_0_0_3_1.topox'

# 用于声明脚本共用的变量或方法，不能修改类名。
# 变量或方法都要定义为类属性，不要定义为实例属性。
class CVarsAndFuncs:

    @classmethod
    def generate_random_ip(cls, address_type='unicast', num_addresses=1, *args)-> list:
        '''
            根据传入的*args地址类型随机产生一个排除了*args包含IP地址且对应*args版本的IP地址；
            支持单播、组播、any类型;
            传入的*args必须只为IPv4或IPv6网络地址格式
            ####################################################
            num_addresses参数为地址个数,最后一个可选参数为排除的IP_Network地址

            按照pypilot脚本规范V1.0要求,ip地址只使用1.0.0.0~9.255.255.255、131.0.0.0~139.255.255.255、211.0.0.0~219.255.255.255
            ipv6地址应取2003::1~3fff:ffff::ffff
        '''
        excluded_networks = []
        random_ips = []
        input_version = []
        hostbits = None

        if len(args) == 0:
            atf_logs('传入的地址格式为空', 'warn')
            return []

        # 处理排除的网络列表，只允许全部为IPv4或全部为IPv6
        for x in args:
            try:
                network_temp = ipaddress.ip_network(x, strict=False)
                excluded_networks.append(network_temp)
                input_version.append(network_temp.version)
            except Exception:
                atf_logs(f'必须传入格式为ipnetwork的地址,传入的地址为{args}', 'warn')
                return []
        
        if input_version.count(4) == len(args):
            hostbits = 32
            version = 4
        elif input_version.count(6) == len(args):
            hostbits = 128
            version = 6
        else:
            atf_logs(f'传入的网络地址非全部为IPv4地址或IPv6地址', 'warn')
            return []

        # 规定的允许范围
        ipv4_ranges = [
            (ipaddress.IPv4Address('1.0.0.0'),   ipaddress.IPv4Address('9.255.255.255')),
            (ipaddress.IPv4Address('131.0.0.0'), ipaddress.IPv4Address('139.255.255.255')),
            (ipaddress.IPv4Address('211.0.0.0'), ipaddress.IPv4Address('219.255.255.255')),
        ]
        ipv6_range_start = ipaddress.IPv6Address('2003::1')
        ipv6_range_end   = ipaddress.IPv6Address('3fff:ffff::ffff')

        max_attempts = 1000 * num_addresses  # 防止死循环

        attempts = 0
        while len(random_ips) < num_addresses and attempts < max_attempts:
            attempts += 1
            if version == 4:
                # 先随机选一个区间，再随机生成
                chosen_range = random.choice(ipv4_ranges)
                ip_int = random.randint(int(chosen_range[0]), int(chosen_range[1]))
                random_ip = ipaddress.IPv4Address(ip_int)
            else:
                ip_int = random.randint(int(ipv6_range_start), int(ipv6_range_end))
                random_ip = ipaddress.IPv6Address(ip_int)

            # 检查是否在排除网络中
            if any(random_ip in net for net in excluded_networks):
                continue
            
            # 按类型过滤
            if address_type == 'unicast':
                if random_ip.is_multicast or random_ip.is_private:
                    continue
            elif address_type == 'multicast':
                if not random_ip.is_multicast:
                    continue
            # any类型不过滤
            
            # 不重复
            if str(random_ip) in random_ips:
                continue

            random_ips.append(str(random_ip))
            # 防止后续重复，动态增加到排除列表
            if version == 4:
                excluded_networks.append(ipaddress.ip_network(str(random_ip) + '/32'))
            else:
                excluded_networks.append(ipaddress.ip_network(str(random_ip) + '/128'))

        if len(random_ips) < num_addresses:
            atf_logs(f'生成数量不足，实际生成{len(random_ips)}个', 'warn')

        atf_logs(f'生成的随机IPv{version}地址为{random_ips}', 'info')
        return random_ips
    
    @classmethod
    def get_available_ip_optimized(cls, network_cidr:str, used_ips:list)-> str:
        '''
            根据指定的IPv4/IPv6子网地址和已知该子网中地址,排除该子网中的已知地址,产生子网中的一个IPv4/IPv6地址
            要求指定的IPv4/IPv6子网地址为网络地址格式,used_ip可以包含网络地址格式和ip地址格式
        '''
        try:
            # 创建一个 IPv4/IPv6 网络对象
            network = ipaddress.ip_network(network_cidr)
        except ipaddress.AddressValueError as e:
            atf_logs(f"get_available_ip_optimized中的network_cidr参数为无效的IPv4/IPv6网络地址:{e}",'error')
            return None
        #对于传入参数为包含IPv4Network/IPv6Network需要转化
        used_ip_addresses = set()
        for item in used_ips:
            try:
                #尝试将地址初始化为IPv4Network/IPv6Network对象
                item = ipaddress.ip_network(item,strict=False)
                #展开网络中的所有地址包括广播地址，加入到集合中
                used_ip_addresses.update(item.hosts())
                used_ip_addresses.add(item.broadcast_address)
                used_ip_addresses.add(item.network_address)
            except ipaddress.AddressValueError as e:
                atf_logs(f"get_available_ip_optimized中为无效的IPv4/IPv6网络地址:{e}",'warn')
        
        # 返回第一个可用的IP地址
        for ip in network.hosts():
            if ip in used_ip_addresses:
                continue
            else:
                return str(ip)

        return None
    
    @classmethod
    def my_get_limit(cls, cmd_return: str) -> int:
        '''功能:通过获取命令参数的限制范围随机产生一个数值,包括最大值和最小值'''
        result = None
        pattern  = r'<(\d+)-(\d+)>'
        matches = re.findall(pattern, cmd_return)
        #必需获取到最小值和最大值
        try:
            limits = matches[0]
            if  len(limits) == 2 and isinstance(limits, tuple):
                result = random.randint( int(limits[0]),int(limits[1]) )
            else:
                atf_logs(f'''
                使用函数my_get_limit产生参数随机值失败,请查看极限获取的范围{limits}''','warn')
            cls.my_get_limit_bottom = int(limits[0])
            cls.my_get_limit_top = int(limits[1])
            return result
        except TypeError:
                atf_logs(f'''
                使用函数my_get_limit对命令参数取最大值和最小值错误,请查看!
                实际取值为{matches}''','warn')
    
    @classmethod
    def random_str(cls, lens: int)-> str:
        '''
        根据指定长度产生随机字符串,对于认证密钥,两个右斜杠符号视为单个符号因此需要所有该符号倍增
        '''
        special_chars = "!@#$%^&*()_+=-[]{}|:;<>,./\\"
        all_chars = string.ascii_letters + string.digits + special_chars
        non_backslash_chars = string.ascii_letters + string.digits + "!@#$%^&*()_+=-[]{}|:;<>,./"
        while True:
            random_string = ''.join(random.choice(all_chars)  for _ in range(lens))
            # print(f'初始字符串:{random_string}')
            # 检查 '\\' 数量及其位置
            total_backslashes = random_string.count('\\')
            backslash_count_end = 0
            # 输出字符串需要将 '\\' 视为2个字符,因此需注意
            if total_backslashes > 0:
                random_string = random_string.replace('\\','\\\\')
                # random_string = random_string.replace('\\\\','\\')
                # random_string = random_string.replace('\\','\\\\')
                # 密钥尾部不能存在奇数个 '\\' 字符
                for char in reversed(random_string):
                    if char == '\\':
                        backslash_count_end += 1
                    else:
                        break
                if backslash_count_end % 2 == 1:
                    continue
                else:
                    break
            elif total_backslashes == 0:
                if lens == len(random_string):
                    # 确保总长度符合, 当前无 '\\'。
                    break
        atf_logs(f'''最终生成的随机字符串为: {random_string}
        生成的字符串长度为{len(random_string)}''','info')
        return random_string
    
    @classmethod
    def normalize_mac_address(cls, mac: str) -> Tuple[list, str]:
        """
        规范化 MAC 地址，将其转换为统一的字节列表格式，并返回原始分隔符。

        参数：
        - mac (str): 原始 MAC 地址字符串。

        返回：
        - tuple: (字节列表, 分隔符)
        """
        # 去除所有可能的分隔符，保留纯十六进制字符
        clean_mac = re.sub(r"[^0-9A-Fa-f]", "", mac)
        
        # 检查字符长度是否是 12（6 字节 * 2 个字符）
        if len(clean_mac) != 12:
            raise ValueError(f"Invalid MAC address format: {mac}")

        # 检测原始分隔符
        if ":" in mac:
            separator = ":"
        elif "-" in mac:
            separator = "-"
        else:
            separator = None  # 无分隔符格式

        # 将纯十六进制字符串分割为字节
        mac_bytes = [int(clean_mac[i:i+2], 16) for i in range(0, 12, 2)]
        return mac_bytes, separator

    @classmethod
    def format_mac_address(cls, mac_bytes: list, separator: str) -> str:
        """
        将字节列表格式化为指定分隔符的 MAC 地址。

        参数：
        - mac_bytes (list): MAC 地址的字节列表。
        - separator (str): 分隔符（可以是 ":" 或 "-" 或 None)。

        返回：
        - str: 格式化后的 MAC 地址字符串。
        """
        if separator == ":":
            return ":".join(f"{byte:02X}" for byte in mac_bytes)
        elif separator == "-":
            mac_str = "".join(f"{byte:02X}" for byte in mac_bytes)
            return "-".join(mac_str[i:i+4] for i in range(0, len(mac_str), 4))
        else:  # 无分隔符
            return "".join(f"{byte:02X}" for byte in mac_bytes)

    @classmethod
    def increment_mac_address(cls, mac: str, increment: int = 1) -> str:
        """
        解析、操作并格式化 MAC 地址。

        参数：
        - mac (str): 原始 MAC 地址字符串，可以是任意格式。
        - increment (int): 递增的步长，默认为 1。

        返回：
        - str: 递增后的 MAC 地址字符串。
        """
        # 调用类方法 normalize_mac_address
        mac_bytes, separator = cls.normalize_mac_address(mac)

        # 从最低有效字节开始递增或递减
        carry = increment
        for i in range(len(mac_bytes) - 1, -1, -1):
            new_value = mac_bytes[i] + carry
            if new_value > 0xFF:
                mac_bytes[i] = new_value & 0xFF
                carry = 1  # 处理进位
            elif new_value < 0:
                mac_bytes[i] = (new_value + 0x100) & 0xFF
                carry = -1  # 处理借位
            else:
                mac_bytes[i] = new_value
                carry = 0

        # 如果仍有进位，则说明地址超出范围
        if carry != 0:
            raise ValueError("Resulting MAC address is out of valid range.")

        # 调用类方法 format_mac_address
        return cls.format_mac_address(mac_bytes, separator)
    
    @classmethod
    def increment_ip(cls, ip_with_mask: str,increment: int = 1) :
        """
        按照指定网络地址递增或递减指定个数子网后,新网络地址。

        参数：
        - ip_with_mask (IPv4或IPv6Network): 初始IP网络地址格式字符串。
        - increment (int): 递增的步长，默认为 1。

        返回：
        - Network: 按照当前网络的子网大小递增/递减的IPv4Network对象。
        """
        # 解析网络地址
        network = ipaddress.ip_network(ip_with_mask, strict=False)
        
        # 获取当前网络的网络地址和子网大小
        network_address = int(network.network_address)
        subnet_size = network.num_addresses
        prefixlen = network.prefixlen
        # 计算下一个或几个子网的网络地址
        try:
            next_network_address = network_address + subnet_size * increment
        except Exception as e:
            atf_logs(f'输入初始网络地址和递增子网个数,计算异常:{e}','error')
            return None
        
        # 转换回 IPv4或IPv6 Network 地址格式
        next_ipnet = ipaddress.ip_network((next_network_address, prefixlen))
        
        return next_ipnet
    
    @classmethod
    def step_subnet(cls, subnet1: str, subnet2: str) -> int:
        '''
        计算两个同掩码的ipv4network之间,子网跳变个数
        '''
        subnet1 = ipaddress.ip_network(subnet1, strict=False)
        subnet2 = ipaddress.ip_network(subnet2, strict=False)
        # 计算两个子网的起始地址的差值
        address_difference = int(subnet2.network_address) - int(subnet1.network_address)

        # 每个子网的地址数量
        addresses_per_subnet = subnet1.num_addresses

        # 计算子网数量
        num_subnets_between = address_difference // addresses_per_subnet
        return num_subnets_between
    
    @classmethod
    def read_yaml(cls) -> dict:
        """
        读取测试仪业务配置,YAML文件并返回字典格式的内容。

        :param file_path: YAML 文件路径
        :return: 字典格式的内容
        """
        def _read_yaml(file_path=None):
            if file_path is None:
                file_path = os.path.dirname(os.path.abspath(__file__))
                file_path = f"{file_path}/config/config.yaml"
            print(f"Current working directory: {os.getcwd()}")
            print(f"File path: {file_path}")
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    data = yaml.safe_load(file)
                    print(f'读取的YAML返回值是:{data}')
                    return data
            except FileNotFoundError:
                atf_logs(f"文件未找到：{file_path}",'error')
                return None
            except yaml.YAMLError as exc:
                atf_logs(f"解析 YAML 文件时出错: {exc}",'error')
                return None
        return _read_yaml

    @classmethod
    def clock_to_s(cls,clock: str) -> int:
        """
        将设备上UTC时间转化为ms
        """
        utc_pattern  = r'(\d+:\d+:\d+)\s+UTC.+?(\d+/\d+/\d+)'
        tz_pattern = r'(\d+:\d+:\d+)\s+(.+)?(\d+/\d+/\d+)'
        match_1 = re.search(utc_pattern, clock)
        if match_1:
            temp = datetime.strptime(match_1.group(0), "%H:%M:%S UTC %a %m/%d/%Y")
            time_transted = int(temp.replace(tzinfo=timezone.utc).timestamp())
        else:
            raise TypeError(f'输入的字符串{clock}非UTC时间格式!')

    @classmethod
    def parse_bgp_version(cls,bit_value) -> str:
        """
        解析bgpVersion位向量,返回支持的BGP版本列表。
        :param bit_value: int或str类型，bgpVersion的十进制值
        :return: list，支持的BGP版本号
        """
        
        if isinstance(bit_value, str):
            bit_value = int(bit_value, 16)
        elif isinstance(bit_value, int):
            pass
        else:
            raise ValueError("bit_value 必须是 int 或 16进制字符串")
        versions = []
        for i in range(8):  # 假设BGP verbison当前最高只支持8位bit,一个字节
            if bit_value & (1 << (7 - i)):
                versions.append(str(i + 1))
        atf_logs(f'当前设备获取的BGP支持versions为:{versions}')
        return versions
    @classmethod
    def get_nested_value(cls, d: dict, target_key: str):
        """
        从嵌套字典/列表中获取指定键的值。
        """
        if isinstance(d, dict):
            for key, value in d.items():
                if key == target_key:
                    return value
                nested_result = cls.get_nested_value(value, target_key)
                if nested_result is not None:
                    return nested_result
        elif isinstance(d, list):
            for item in d:
                nested_result = cls.get_nested_value(item, target_key)
                if nested_result is not None:
                    return nested_result
        return None
    classmethod
    def generate_random_net(area_len : int = 2) -> str:
        '''
        随机产生isis进程的Network Entity值
        参数: area为想要使用的Bytes*2长度
        '''
        # area长度为1-3 Bytes,此时取用最小长度1 Bytes
        area_id = ''.join(random.choices('0123456789abcdef', k=area_len))

        system_id_segments = [''.join(random.choices('0123456789abcdef', k=4)) for _ in range(3)]

        n_selector = '00'

        net = f"{area_id}.{system_id_segments}.{n_selector}"
        return net


# 不能删除setup/teardown的装饰器
@atf_time_stats("ATFSetupTime")
@atf_adornment
def setup():
    pass

@atf_time_stats("ATFTeardownTime")
@atf_adornment
def teardown():
    pass


#用于用户自定义在测试用例前后/用例间/步骤前后/步骤间自定义操作，配合设置开关使用，举例：--extra-operations="{'class':True}"
@decorator_for_user_log()
def pre_func():
    pass

@decorator_for_user_log()
def post_func():
    pass
# ---------END-----------

@fixture(scope="package", autouse=True)
def my_fixture_setup_and_teardown():
    atf_topo_map(topo, level)
    try:
        setup()
        yield
    finally:
        teardown()
        atf_topo_unmap()


@fixture(scope="package")
def VarsAndFuncs():
    return CVarsAndFuncs